home *** CD-ROM | disk | FTP | other *** search
/ PsL Monthly 1993 December / PSL Monthly Shareware CD-ROM (December 1993).iso / prgmming / dos / pascal / viaray.com / VIARAY.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1990-11-09  |  18.1 KB  |  465 lines

  1. unit ViAray;  { John Haluska,  CIS 74000,1106 }      { Turbo Pascal 5.0, 5.5 }
  2. {$R-,S-,V-,I+,B-,F-,A+,D+,N-,L+}
  3.  
  4. { Ver 1.0  10/1/90  Released to public domain  }
  5.  
  6. { This unit is derived from Buffered Generic VirtualArray Object, a public
  7.   domain program by Eric C. Wentz, CIS 72070,1015.
  8.  
  9.   The ViAray unit is a high performance virtual array manager which uses
  10.   8 RAM buffers to access a data array in a disk file.  Each array element may
  11.   be any type and have a size of 1 to 32767 bytes.  The array size (element
  12.   size times number of elements) is limited only by the DOS file size limit
  13.   (typically 32 MBytes).  The data file contains the data array only.  Typical
  14.   use:
  15.  
  16.    1.  Define the array element data structure (integer, real, record, etc).
  17.    2.  Define a fatal error exit procedure.  See Error procedure for example.
  18.    3.  Prepare for a new or existing array, the number of elements, element
  19.        size, RAM buffer size, and array file name with the Init procedure.  An
  20.        array must be prepared with Init before any of the following procedures
  21.        can be used.
  22.    4.  Write data into an array element with the Accept procedure.
  23.    5.  Read data from an array element with the Retrieve procedure.
  24.    6.  Exchange data between two array elements with the Swap procedure.
  25.    7.  Increase number of array elements and/or change RAM buffer size with
  26.        the ReSize procedure.
  27.    8.  Transfer contents of one array element to another array element for the
  28.        same or different arrays with the Copy procedure.
  29.    9.  Remove RAM buffer and close array file with the Done procedure.
  30.   10.  Remove RAM buffer and delete array file with the Destroy procedure.
  31.  
  32.   Each VirArray variable is allocated 8 sectors with each sector having 1/8 of
  33.   the specified RAM buffer assigned to it.  Buffers freely move within their
  34.   assigned sector, but they can never read from or write to adjacent sectors.
  35.   To save access time, a buffer never writes to disk unless the buffer data
  36.   has been changed, with the exception of the ReSize, Done and Destroy
  37.   procedures, which write all buffers of the VirArray variable to disk.
  38.  
  39.   The maximum total buffer size is 524,168 bytes, and is determined by
  40.   available heap RAM and by the GetMem limit of 65521 bytes for a single
  41.   structure.
  42.  
  43.   There are 3 major influences on the performance characteristics of the
  44.   VirArray.  The first is load factor or the actual percentage of the disk
  45.   file which resides in RAM.  The second is the size of the individual buffers
  46.   themselves.  As the size of the buffers increases, the time required to read
  47.   or write each buffer from or to disk also increases.  With a high load factor
  48.   this is not much of a problem, but with a low load factor and a lot of random
  49.   accesses, much time will be spent reading or writing buffers.  The third is
  50.   proportional to the file size, and is the time required to seek a random
  51.   address within the file before reading or writing.  Serial and closely
  52.   spaced accessing is always quite good unless the buffers are very small.  }
  53.  
  54. interface
  55.  
  56. uses
  57.   Dos;
  58.  
  59. const
  60.   ErrMsg : string[79] = '';                              {termination message}
  61.  
  62. type
  63.  
  64.   Space  = array[0..0] of byte;              {abstract 0 based array of bytes}
  65.  
  66.   VirArray = record                 {do not modify record variables directly!}
  67.     ElSize    : word;                            {bytes in each array element}
  68.     NumElems  : longint;                            {number of array elements}
  69.     Name      : PathStr;                       {filename with drive/directory}
  70.     DriveNum  : word;                      {disk drive number (1=A, 2=B, etc)}
  71.     F         : file;                                 {assigned file variable}
  72.     BSize     : word;                         {bytes in each of 8 RAM buffers}
  73.     SSize     : longint;       {(ElSize*NumElems)/8; adj for partial elements}
  74.     Buffer    : array[0..7] of ^Space;               {addr of each RAM buffer}
  75.     UpDate    : array[0..7] of boolean; {true if file data <> RAM buffer data}
  76.     BuffLeft  : array[0..7] of longint;          {1st byte of each RAM buffer}
  77.   end;
  78.  
  79. procedure Init(var V : VirArray; NewArray : boolean; NumElements : longint;
  80.                ElementSize : word; MaxBuffSize : longint; FileName : PathStr);
  81. procedure Accept(var V : VirArray; var ElData; Index : longint);
  82. procedure Retrieve(var V : VirArray; var ElData; Index : longint);
  83. procedure Copy(var V1,V2 : VirArray; I1,I2 : longint);
  84. procedure Swap(var V : VirArray; I,J : longint);
  85. procedure ReSize(var V : VirArray; NumElements,MaxBuffSize : longint);
  86. procedure Done(var V : VirArray);
  87. procedure Destroy(var V : VirArray);
  88.  
  89. implementation
  90.  
  91. const
  92.   MaxRamBuffer = 524168;   {8 * 65521}
  93.  
  94. {----------------------------------------------------------------------------}
  95. { Error places message number N with string St in unit global variable ErrMsg
  96.   and terminates program when this procedure is called.  The ErrMsg string is
  97.   typically used in an exit procedure in the main program.  }
  98.  
  99. (*  Example:   var
  100.                  ExitSave : pointer;
  101.                {$F+} procedure PrgmExit;
  102.                  begin
  103.                    ExitProc := ExitSave;
  104.                    if ErrMsg <> '' then Writeln(#13,#10,ErrMsg);
  105.                  end;  {$F-}
  106.                begin {Main}
  107.                  ExitSave := ExitProc;
  108.                  ExitProc := @PrgmExit;
  109.                  ----
  110.                end.  {Main}    *)
  111.  
  112. procedure Error(N : byte; St : string);
  113.  
  114. begin
  115.   case N of
  116.      1 : ErrMsg := 'Unable to open file '+ St;
  117.      2 : ErrMsg := 'Array element sizes do not match for operation '+ St;
  118.      3 : ErrMsg := 'Index out of bounds for operation ' + St;
  119.      4 : ErrMsg := 'Array file not open';
  120.      6 : ErrMsg := 'Insufficient free disk space for operation ' + St;
  121.      7 : ErrMsg := 'Insufficient RAM for operation ' + St;
  122.     10 : ErrMsg := 'Buffer size too small or insufficient memory';
  123.   end;
  124.   Halt(0)
  125. end;  {Error}
  126. {----------------------------------------------------------------------------}
  127. { InBuff returns true if array V element Index is in RAM buffer Buff.  }
  128.  
  129. function InBuff(var V : VirArray; Index : longint; Buff : byte) : boolean;
  130.  
  131. var
  132.   L : longint;
  133. begin
  134.   L := Index*V.ElSize;
  135.   if (L >= V.BuffLeft[Buff]) and (L < (V.BuffLeft[Buff] + V.BSize)) then
  136.     InBuff := true
  137.   else
  138.     InBuff := false
  139. end;  {InBuff}
  140. {----------------------------------------------------------------------------}
  141. { FlushBuff writes array V RAM buffer number Buff to disk file.  }
  142.  
  143. procedure FlushBuff(var V : VirArray; Buff : byte);
  144.  
  145. begin
  146.   Seek(V.F,V.BuffLeft[Buff]);
  147.   BlockWrite(V.F,V.Buffer[Buff]^,V.BSize)
  148. end;  {FlushBuff}
  149. {----------------------------------------------------------------------------}
  150. { RemoveBuffers stores all 8 RAM buffers into array V disk file and
  151.   deallocates RAM.  }
  152.  
  153. procedure RemoveBuffers(var V : VirArray);
  154. var
  155.   I : byte;
  156. begin
  157.   for I := 0 to 7 do
  158.     begin
  159.       FlushBuff(V,I);
  160.       FreeMem(V.Buffer[I],V.BSize)
  161.     end
  162. end;  {RemoveBuffers}
  163. {----------------------------------------------------------------------------}
  164. { LoadBuff reads array V data from disk file into RAM buffer number Buff. }
  165.  
  166. procedure LoadBuff(var V : VirArray; Buff : byte);
  167.  
  168. begin
  169.   Seek(V.F,V.BuffLeft[Buff]);
  170.   BlockRead(V.F,V.Buffer[Buff]^,V.BSize)
  171. end;  {LoadBuff}
  172. {----------------------------------------------------------------------------}
  173. { MoveBuff writes RAM buffer number Buff to disk if it has been changed.
  174.   MoveBuff then sets the location of RAM buffer number Buff so that array V
  175.   element Index is in the middle of Buff.  If necessary, Buff location is
  176.   adjusted to keep it in the array sector assigned to Buff.  MoveBuff then
  177.   reads data from disk file into Buff.}
  178.  
  179. procedure MoveBuff(var V : VirArray; Index : longint; Buff : byte);
  180.  
  181. var
  182.   Base,J : longint;
  183. begin
  184.   if V.UpDate[Buff] then
  185.     begin                              {write data in RAM buffer to disk file}
  186.       FlushBuff(V,Buff);
  187.       V.UpDate[Buff] := false
  188.     end;
  189.   if V.BSize > V.ElSize then      {each RAM buffer contains multiple elements}
  190.     begin
  191.       Base := (Index * V.ElSize) - (V.BSize div 2);     {center Buff on Index}
  192.       Base := Base - (Base mod V.ElSize);     {start Buff on Element boundary}
  193.       case Buff of             {if reqd, clamp Buff at top of assigned sector}
  194.         0..6 : begin
  195.                  J := V.SSize * (Buff+1);
  196.                  if (Base + V.BSize) >= J then Base := J - V.BSize
  197.                end;
  198.            7 : begin
  199.                  J := V.NumElems * V.ElSize;
  200.                  if (Base + V.BSize) >= J then Base := J - V.BSize
  201.                end
  202.       end;
  203.       J := V.SSize * Buff;
  204.       if Base < J then Base := J     {if reqd, clamp Buff at bottom of sector}
  205.     end
  206.   else
  207.     Base := Index * V.ElSize;             {each RAM buffer contains 1 element}
  208.   V.BuffLeft[Buff] := Base;
  209.   LoadBuff(V,Buff)
  210. end;  {MoveBuff}
  211. {----------------------------------------------------------------------------}
  212. { Sector returns the sector number (0-7) of the RAM buffer for array V element
  213.   Index. }
  214.  
  215. function Sector(var V : VirArray; Index : longint) : byte;
  216.  
  217. var
  218.   I : integer;
  219.   Test,Temp : longint;
  220. begin
  221.   I := -1;
  222.   Test := 0;
  223.   Temp := V.ElSize * Index;
  224.   while Test <= Temp do
  225.     begin
  226.       Inc(I);
  227.       Inc(Test,V.SSize)
  228.     end;
  229.   if I > 7 then I := 7;
  230.   Sector := byte(I)
  231. end;  {Sector}
  232. {----------------------------------------------------------------------------}
  233. { SetupBuffers initializes the SSize/BSize variables, the BuffLeft/UpDate
  234.   arrays, and allocates the RAM buffers for array V.  }
  235.  
  236. procedure SetupBuffers(var V : VirArray; BuffSize : longint);
  237.  
  238. var
  239.   TotData : longint;
  240.   Buffers : byte;
  241. begin
  242.   if BuffSize = 0 then BuffSize := MaxAvail - 1024;     {max heap - 1024}
  243.   if BuffSize > MaxRamBuffer then BuffSize := MaxRamBuffer;
  244.   TotData := V.ElSize * V.NumElems;
  245.   V.BSize := BuffSize div 8;
  246.   if (longint(V.BSize) * 8) > TotData then
  247.     V.BSize := TotData div 8;            {all array elements fit in RAM buffer}
  248.   V.SSize := TotData div 8;
  249.   V.SSize := V.SSize - (V.SSize mod V.ElSize);   {partial elements not allowed}
  250.   if V.BSize > V.SSize then V.BSize := V.SSize; {all array elements fit in RAM}
  251.   V.BSize := V.BSize - (V.BSize mod V.ElSize);   {partial elements not allowed}
  252.   if (V.BSize <= 0) or (V.SSize <= 0) then Error(10,'');
  253.   for Buffers := 0 to 7 do                                   {init RAM buffers}
  254.     begin
  255.       V.BuffLeft[Buffers] := Buffers*V.SSize;
  256.       GetMem(V.Buffer[Buffers],V.BSize);
  257.       if V.Buffer[Buffers] = nil then Error(7,'SetupBuffers');
  258.       LoadBuff(V,Buffers);
  259.       V.UpDate[Buffers] := false
  260.     end;
  261. end;  {SetupBuffers}
  262. {----------------------------------------------------------------------------}
  263. { Initialize RAM buffers and open disk file for a new (NewArray = true) or
  264.   existing (NewArray = false) array V with NumElements elements, ElementSize
  265.   size in bytes, MaxBuffSize (in bytes) of RAM buffer, and disk file FileName.
  266.   FileName can include the drive and directory.  If MaxBuffSize = 0 then all
  267.   available RAM, less 1KB, will be used.  If an existing array, NumElements
  268.   can be any number.  Remove RAM buffers and close disk file with Done or
  269.   Destroy procedures.
  270.   Example:  var A : VirArray; Init(A,true,2000,2,1000,'A.DAT') initializes a
  271.   new array[0..1999] with elements of 2 bytes each and a RAM buffer of 1000
  272.   bytes stored in diskfile A.DAT.  }
  273.  
  274. procedure Init(var V : VirArray; NewArray : boolean; NumElements : longint;
  275.                ElementSize : word; MaxBuffSize : longint; FileName : PathStr);
  276. var
  277.   TotData,J,K : longint;
  278.   Buff : ^Space;
  279.   L,BuffSize : word;
  280.  
  281. begin
  282.  
  283.   {---Setup File---}
  284.   V.Name := FExpand(FileName);
  285.   V.DriveNum := Ord(V.Name[1]) - 64;         {drive number 1 = A, 2 = B, etc}
  286.   if NewArray then
  287.     begin
  288.       TotData := NumElements*ElementSize;
  289.       if TotData > DiskFree(V.DriveNum) then Error(6,'Init')
  290.     end;
  291.   Assign(V.F,V.Name);
  292.   {$I-} if NewArray then Rewrite(V.F,1) else Reset(V.F,1); {$I+}
  293.   if IOResult <> 0 then Error(1,V.Name);
  294.   if NewArray then
  295.     begin
  296.       if TotData < 65521 then BuffSize := word(TotData) else BuffSize := 65521;
  297.       if BuffSize > MaxAvail then BuffSize := MaxAvail;
  298.       if BuffSize = 0 then Error(7,'Init');
  299.       GetMem(Buff,BuffSize);
  300.       for L := 0 to BuffSize-1 do Buff^[L] := 0;        {init buffer contents}
  301.       K := TotData div BuffSize;
  302.       for J := 0 to K-1 do                                   {TotData > 65521}
  303.         BlockWrite(V.F,Buff^,BuffSize);
  304.       L := word(TotData - (K*BuffSize));
  305.       if L >= 0 then        {(TotData <= 65521) or (TotData mod BuffSize > 0)}
  306.         BlockWrite(V.F,Buff^,L);
  307.       FreeMem(Buff,BuffSize)
  308.     end
  309.   else
  310.     begin
  311.       TotData := FileSize(V.F);
  312.       if TotData mod ElementSize <> 0 then Error(2,'Init existing array')
  313.         else NumElements := TotData div ElementSize;
  314.     end;
  315.  
  316.   {---Setup Buffers---}
  317.   V.NumElems := NumElements;
  318.   V.ElSize := ElementSize;
  319.   SetupBuffers(V,MaxBuffSize);
  320. end;  {Init}
  321. {----------------------------------------------------------------------------}
  322. { Accept loads data ElData into array V element Index.  ElData can be a
  323.   variable of any type (real, integer, record, etc) with element size
  324.   specified by the Init procedure.
  325.  
  326.   Example: type  ElTyp = record        (16 bytes)
  327.                    Name : string[11];
  328.                    ID   : longint;
  329.                  end;
  330.            var  A : VirArray;  D : ElTyp;
  331.  
  332.            D.Name := 'Smith';  D.Id := 12345;
  333.            Accept(A,D,34);  loads Smith, 12345 into array A element 34   }
  334.  
  335. procedure Accept(var V : VirArray; var ElData; Index : longint);
  336.  
  337. var
  338.   Buf : Space absolute ElData;
  339.   Sect : byte;
  340. begin
  341.   if (Index >= V.NumElems) or (Index < 0) then Error(3,'Accept');
  342.   Sect := Sector(V,Index);
  343.   if not InBuff(V,Index,Sect) then MoveBuff(V,Index,Sect);
  344.   Move(Buf,V.Buffer[Sect]^[(Index*V.ElSize)-V.BuffLeft[Sect]],V.ElSize);
  345.   V.UpDate[Sect] := true
  346. end;  {Accept}
  347. {----------------------------------------------------------------------------}
  348. { Retrieve data ElData from array V element Index. }
  349.  
  350. procedure Retrieve(var V : VirArray; var ElData; Index : longint);
  351.  
  352. var
  353.   Buf : Space absolute ElData;
  354.   Sect : byte;
  355. begin
  356.   if (Index >= V.NumElems) or (Index < 0) then Error(3,'Retrieve');
  357.   Sect := Sector(V,Index);
  358.   if not InBuff(V,Index,Sect) then MoveBuff(V,Index,Sect);
  359.   Move(V.Buffer[Sect]^[(Index*V.ElSize)-V.BuffLeft[Sect]],Buf,V.ElSize)
  360. end;  {Retrieve}
  361. {----------------------------------------------------------------------------}
  362. { Copy array V1 element I1 to array V2 element I2.  Arrays V1 and V2 may be
  363.   the same array.  If different arrays, each array must have the same element
  364.   size.  Example:  var A1,A2 : VirArray;  Copy(A1,A2,1,20) copies array A1
  365.   element 1 into array A2 element 20.}
  366.  
  367. procedure Copy(var V1,V2 : VirArray; I1,I2 : longint);
  368.  
  369. var
  370.   T1 : ^Space;
  371. begin
  372.   if V1.ElSize <> V2.ElSize then Error(2,'Copy');
  373.   GetMem(T1,V1.ElSize);
  374.   if T1 = nil then Error(7,'Copy');
  375.   Retrieve(V1,T1^,I1);
  376.   Accept(V2,T1^,I2);
  377.   FreeMem(T1,V1.ElSize)
  378. end;  {Copy}
  379. {----------------------------------------------------------------------------}
  380. { Swap data in array V elements I and J.  Example:  var A : VirArray;
  381.   Swap(A,5,10)  exchanges data between array elements 5 and 10.  }
  382.  
  383. procedure Swap(var V : VirArray; I,J : longint);
  384.  
  385. var
  386.   T1,T2 : ^Space;
  387. begin
  388.   GetMem(T1,V.ElSize);
  389.   GetMem(T2,V.ElSize);
  390.   if (T1=nil) or (T2=nil) then Error(7,'Swap');
  391.   Retrieve(V,T1^,I);
  392.   Retrieve(V,T2^,J);
  393.   Accept(V,T1^,J);
  394.   Accept(V,T2^,I);
  395.   FreeMem(T1,V.ElSize);
  396.   FreeMem(T2,V.ElSize)
  397. end;  {Swap}
  398. {----------------------------------------------------------------------------}
  399. { ReSize increases the number of array V elements NumElements and changes the
  400.   MaxBuffSize in bytes of the RAM buffer in array V.  Array V must be
  401.   initialized with Init. }
  402.  
  403. procedure ReSize(var V : VirArray; NumElements,MaxBuffSize : longint);
  404.  
  405. var
  406.   ElemIncr,K,J : longint;
  407.   L,BufSize : word;
  408.   Buf : ^Space;
  409.  
  410. begin
  411.   RemoveBuffers(V);                             {remove existing RAM buffers}
  412.   if NumElements > V.NumElems then
  413.     begin
  414.       ElemIncr := (NumElements - V.NumElems) * V.ElSize;
  415.       if DiskFree(V.DriveNum) < ElemIncr then Error(6,'ReSize');
  416.       if ElemIncr < 65521 then BufSize := word(ElemIncr) else BufSize := 65521;
  417.       if BufSize > MaxAvail then BufSize := MaxAvail;
  418.       GetMem(Buf,BufSize);
  419.       for L := 0 to BufSize-1 do Buf^[L] := 0;          {init element contents}
  420.       Seek(V.F,FileSize(V.F));              {move file position to end of file}
  421.       K := ElemIncr div BufSize;
  422.       for J := 0 to K-1 do                                   {ElemIncr > 65521}
  423.         BlockWrite(V.F,Buf^,BufSize);
  424.       L := word(ElemIncr - (K*BufSize));
  425.       if L >= 0 then       {(ElemIncr <= 65521) or (ElemIncr mod BuffSize > 0)}
  426.         BlockWrite(V.F,Buf^,L);
  427.       FreeMem(Buf,BufSize);
  428.     end;
  429.   V.NumElems := NumElements;
  430.   SetupBuffers(V,MaxBuffSize)                               {setup RAM buffers}
  431. end; {ReSize}
  432. {----------------------------------------------------------------------------}
  433. { Done stores array V RAM buffers to disk, deallocates heap memory and closes
  434.   the array file.  Example:  var A : VirArray;  Store(A);  }
  435.  
  436. procedure Done(var V : VirArray);
  437.  
  438. begin
  439.   RemoveBuffers(V);
  440.   {$I-} Close(V.F);  {$I+}
  441.   if IOResult <> 0 then Error(4,'')
  442. end;  {Done}
  443. {----------------------------------------------------------------------------}
  444. { Destroy (delete) the array V file on disk, remove RAM buffers, and
  445.   deallocate heap memory.  Example:  var A : VirArray;  Destroy(A);  }
  446.  
  447. procedure Destroy(var V : VirArray);
  448.  
  449. begin
  450.   Done(V);
  451.   Erase(V.F);
  452. end;  {Destroy}
  453. {----------------------------------------------------------------------------}
  454. { HeapErrorTrap causes New and GetMem to return nil if out of heap memory. }
  455.  
  456. {$F+} function HeapErrorTrap(Size : word) : integer;
  457.  
  458. begin
  459.   HeapErrorTrap := 1
  460. end; {$F-}
  461. {----------------------------------------------------------------------------}
  462. begin
  463.   HeapError := @HeapErrorTrap
  464. end. {ViAray}
  465.